﻿//
// Helper.cs: Contains some methods that are potentially useful to 
// projects that reference S9PalCommon.
//
// Copyright 2010 Michael Goethe
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.Serialization.Formatters.Binary;

namespace S9PalCommon
{
    /// <summary>
    /// Contains some methods that are potentially useful to projects that reference S9PalCommon.
    /// </summary>
    public static class Helper
    {
        #region Public Methods
        /// <summary>
        /// Deserializes an object from a specified file name.
        /// </summary>
        /// <typeparam name="T">Type to deserialize.</typeparam>
        /// <param name="fileName">File name to deserialize from.</param>
        /// <returns>Deserialized object.</returns>
        public static T DeserializeObject<T>(String fileName)
        {
            T deserializedObject = default(T);
            Stream stream = null;
            BinaryFormatter bFormatter = new BinaryFormatter();
            bFormatter.Binder = new VersionAgnosticSerializationBinder();

            try
            {
                stream = File.Open(fileName, FileMode.Open);
                deserializedObject = (T)bFormatter.Deserialize(stream);
            }
            finally
            {
                if (stream != null) stream.Close();
            }

            return deserializedObject;
        }

        /// <summary>
        /// Used like the Directory.GetFiles method, but allows for multiple search patterns.
        /// </summary>
        /// <param name="path">The directory to search.</param>
        /// <param name="searchPattern">Search pattern or multiple search patterns separated by a semi-colon.</param>
        /// <param name="searchOption">SearchOption specifying whether to do a "deep" search or only look in the top directory.</param>
        /// <returns>An array of file paths.</returns>
        public static String[] GetFilesWithMultiplePatterns(String path, String searchPattern, SearchOption searchOption)
        {
            // Split apart the extensions into an array.
            String[] extensions = searchPattern.Replace(" ", "").Split(';');
            List<String> files = new List<String>();

            // Using each extension, call the the GetFiles method.
            foreach (String filter in extensions)
                files.AddRange(System.IO.Directory.GetFiles(path, filter, searchOption));

            return files.ToArray();
        }

        /// <summary>
        /// Returns the MD5 hash value of an array of bytes.
        /// </summary>
        /// <param name="data">Array of bytes to get the hash value of.</param>
        /// <returns>String representation of the hash value.</returns>
        public static String GetHashValue(byte[] data)
        {
            MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
            String fileHashValue = ConvertByteArrayToString(md5.ComputeHash(data));

            return fileHashValue;
        }

        /// <summary>
        /// Removes invalid file name characters from a string.
        /// </summary>
        /// <param name="str">String to be sanitized.</param>
        /// <returns>Sanitized string with no invalid characters.</returns>
        public static String RemoveInvalidFileNameCharacters(String str)
        {
            String sanitizedString = str;

            // Get rid of "%*/.:;<=>?[\]| (some of these are special characters in regular expressions, so they need an extra backslash in front of them).
            // Also make sure to trim any white space at the beginning or end of the text.
            if (sanitizedString != null)
                sanitizedString = Regex.Replace(str, @"[""%\*/.:;<=>\?\[\\\]\|]", "").Trim();

            return sanitizedString;
        }
        
        /// <summary>
        /// Serializes an object to a specified file name.
        /// </summary>
        /// <typeparam name="T">Type to serialize.</typeparam>
        /// <param name="fileName">File name to serialize to.</param>
        /// <param name="objectToSerialize">Object to serialize.</param>
        public static void SerializeObject<T>(String fileName, Object objectToSerialize)
        {
            Stream stream = null;
            bool retry = false;
            BinaryFormatter bFormatter = new BinaryFormatter();
            bFormatter.Binder = new VersionAgnosticSerializationBinder();

            do
            {
                try
                {
                    stream = File.Open(fileName, FileMode.Create);
                    bFormatter.Serialize(stream, (T)objectToSerialize);
                    retry = false;
                }
                catch (DirectoryNotFoundException)
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(fileName));
                    retry = true;
                }
                finally
                {
                    if (stream != null) stream.Close();
                }
            } while (retry);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Converts an array of bytes to a string.
        /// </summary>
        /// <param name="byteArray">The array of bytes to convert.</param>
        /// <returns>String representation of the hash value.</returns>
        private static String ConvertByteArrayToString(byte[] byteArray)
        {
            StringBuilder hex = new StringBuilder(byteArray.Length * 2);
            foreach (byte b in byteArray)
                hex.AppendFormat("{0:x2}", b);
            return hex.ToString();
        }

        #endregion

        #region Nested Classes

        /// <summary>
        /// Class used to allow deserialization of types hosted in assemblies having different versions.
        /// </summary>
        /// <remarks>The binder will always match requested assembly with currently available one, without knowing if this action is legal or not.</remarks>
        private sealed class VersionAgnosticSerializationBinder : System.Runtime.Serialization.SerializationBinder
        {
            /// <summary>
            /// When overridden in a derived class, controls the binding of a serialized object to a type.
            /// </summary>
            /// <param name="assemblyName">Specifies the <see cref="T:System.Reflection.Assembly"/> name of the serialized object.</param>
            /// <param name="typeName">Specifies the <see cref="T:System.Type"/> name of the serialized object.</param>
            /// <returns>
            /// The type of the object the formatter creates a new instance of.
            /// </returns>
            public override Type BindToType(string assemblyName, string typeName)
            {
                Type type = Type.GetType((System.Reflection.Assembly.CreateQualifiedName(assemblyName, typeName)));

                if (type == null)
                {
                    //A full name was passed, we need the simple one!
                    string simpleAssemblyName = assemblyName.Split(',').FirstOrDefault();

                    if (string.IsNullOrEmpty(simpleAssemblyName))
                        throw new InvalidOperationException("Cannot retrieve assembly name from string '" + assemblyName + "'");

                    System.Reflection.Assembly currentAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((System.Reflection.Assembly assembly) => { return (assembly.GetName().Name.Equals(simpleAssemblyName)); });

                    if (currentAssembly == null)
                        throw new InvalidOperationException("The requested assembly '" + simpleAssemblyName + "' was not found");

                    type = currentAssembly.GetType(typeName);
                }

                if (type == null)
                    System.Diagnostics.Debug.WriteLine("Cannot bind requested type '" + string.Format("{0}, {1}", assemblyName, typeName), GetType().ToString());

                return type;
            }
        }

        #endregion
    }
}
